/**
 * Wrappers for BerkeleyDB
 */
#ifndef XBDB_H
#define XBDB_H
#include <iostream>
#include <memory>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cfloat>
#include <stdint.h>
#include <db.h>
#include "throw.h"

namespace bdb
{

class Cursor;

#define ENTRY_CTOR(type) Entry(type v):len(sizeof(type))\
		{\
		ptr=(char*)malloc(sizeof(type));\
		if(ptr==NULL) THROW("out of memory");\
		std::memcpy((void*)ptr,(void*)&v,sizeof(type));\
		}

#define ENTRY_TO(type,fun) inline type fun() const {\
		if(len< sizeof(type)) THROW("Data out of range");\
		type var;\
		std::memcpy((void*)&var,(void*)ptr,sizeof(type));\
		return var;\
		}

class Entry
	{
    private:
		char* ptr;
		std::size_t len;
    public:
		Entry();
		Entry(const Entry& cp);
		Entry(const DBT* dbt);
		Entry(const void* s,std::size_t len);
		Entry(const char* s);
		Entry(const std::string& s);
		Entry(std::size_t len,int c);
		ENTRY_CTOR(int8_t)
		ENTRY_CTOR(int16_t)
		ENTRY_CTOR(int32_t)
		ENTRY_CTOR(int64_t)
		ENTRY_CTOR(uint8_t)
		ENTRY_CTOR(uint16_t)
		ENTRY_CTOR(uint32_t)
		ENTRY_CTOR(uint64_t)
		ENTRY_CTOR(float)
		ENTRY_CTOR(double)
		~Entry();
		bool empty() const;
		const void* data() const;
		std::size_t size() const;
		Entry& append(const void* p,std::size_t n);
		Entry& write(const void* p,std::size_t n) { return append(p,n);}//alias
		void fill(DBT* dbt) const;
		ENTRY_TO(bool,toBool)
		ENTRY_TO(char,toChar)
		ENTRY_TO(int8_t,toInt8)
		ENTRY_TO(int16_t,toInt16)
		ENTRY_TO(int32_t,toInt32)
		ENTRY_TO(int64_t,toInt64)
		ENTRY_TO(uint8_t,toUInt8)
		ENTRY_TO(uint16_t,toUInt16)
		ENTRY_TO(uint32_t,toUInt32)
		ENTRY_TO(uint64_t,toUInt64)
		ENTRY_TO(float,toFloat)
		ENTRY_TO(double,toDouble)
		std::string toString() const;
		bool operator==(const Entry& cp) const;
		bool operator<(const Entry& cp) const;
		Entry& operator=(const Entry& cp);
		char operator[](std::size_t index) const;
	};

std::ostream& operator<<(std::ostream& out,const Entry& o);
#undef ENTRY_CTOR
#undef ENTRY_TO

#define ENTRYREAD(rettype,fun) inline rettype fun() { rettype var; this->read(&var,sizeof(rettype)); return var;}

class EntryReader
	{
	private:
		const void* entry_data;
		size_t entry_size;
		size_t pos;
	public:
		EntryReader(const Entry* entry);
		EntryReader(const DBT* entry);
		~EntryReader();
		bool empty() const;
		std::size_t readbytes(void *ptr, size_t size);
		EntryReader& read(void *ptr, size_t size);
		ENTRYREAD(bool,readBool)
		ENTRYREAD(char,readChar)
		ENTRYREAD(int8_t,readInt8)
		ENTRYREAD(int16_t,readInt16)
		ENTRYREAD(int32_t,readInt32)
		ENTRYREAD(int64_t,readInt64)
		ENTRYREAD(uint8_t,readUInt8)
		ENTRYREAD(uint16_t,readUInt16)
		ENTRYREAD(uint32_t,readUInt32)
		ENTRYREAD(uint64_t,readUInt64)
		ENTRYREAD(float,readFloat)
		ENTRYREAD(double,readDouble)
		std::string readCString();
		std::string readString8();
		std::string readString16();
		std::string readString32();
	};
#undef ENTRYREAD

#define ENTRYWRITE(rettype,fun) inline EntryWriter& fun(const rettype var) { this->writebytes((void*)&var,sizeof(rettype)); return *this;}

class EntryWriter
	{
	private:
	 	 Entry* entry;
	public:
	 	EntryWriter(Entry* entry);
	 	~EntryWriter();

	 	std::size_t writebytes(const void *ptr, size_t size);
	 	EntryWriter& write(const void *ptr, size_t size);
	 	ENTRYWRITE(bool,writeBool)
	 	ENTRYWRITE(char,writeChar)
	 	ENTRYWRITE(int8_t,writeInt8)
	 	ENTRYWRITE(int16_t,writeInt16)
	 	ENTRYWRITE(int32_t,writeInt32)
	 	ENTRYWRITE(int64_t,writeInt64)
	 	ENTRYWRITE(uint8_t,writeUInt8);
		ENTRYWRITE(uint16_t,writeUInt16)
		ENTRYWRITE(uint32_t,writeUInt32)
		ENTRYWRITE(uint64_t,writeUInt64)
		ENTRYWRITE(float,writeFloat)
		ENTRYWRITE(double,writeDouble)
		EntryWriter& writeCString(const char *ptr);
		EntryWriter& writeString8(const std::string s);
		EntryWriter& writeString16(const std::string s);
		EntryWriter& writeString32(const std::string s);
	};
#undef ENTRYWRITE

#define DB_CMP(a) static int compare##a(DB *db, const DBT *dbt1, const DBT *dbt2)



class Database
	{
	private:
		DB *dbp; /* DB structure handle */
		int flag_status;
	public:
		typedef  int (*bt_comparator)(DB *db, const DBT *dbt1, const DBT *dbt2);

		Database(DB_ENV *dbenv=NULL,u_int32_t flag=0);
		virtual ~Database();
		bool is_open() const;
		virtual void open(DB_TXN *txn,char *file, const char *database, DBTYPE type, u_int32_t flags, int mode=0);
		virtual DB* db();
		virtual uint64_t count(DB_TXN *txn=NULL,u_int32_t flag=0);
		virtual void close();
		virtual int put(DB_TXN *txnid, DBT *key, DBT *data, u_int32_t flags=0);
		virtual int put(DB_TXN *txnid, const Entry *key, const Entry *data, u_int32_t flags=0);
		virtual int get(DB_TXN *txnid, DBT *key, DBT *data, u_int32_t flags=0);
		virtual std::auto_ptr<Entry> get(DB_TXN *txnid, const Entry *key, u_int32_t flags=0);
		virtual bool contains(DB_TXN *txnid, const Entry *key, u_int32_t flags=0);
		virtual int pget(DB_TXN *txnid, DBT *key, DBT *pkey, DBT *data, u_int32_t flags=0);
		virtual int remove(DB_TXN *txnid, DBT *key, u_int32_t flags=0);
		virtual int cursor( DB_TXN *txnid, DBC **cursorp, u_int32_t flags=0);
		virtual std::auto_ptr<Cursor> cursor(DB_TXN *txnid=NULL, u_int32_t flags=0);
		int setFlags(u_int32_t flags);
		Database& setBTreeCompare(bt_comparator fun);
		Database& setDupCompare(bt_comparator fun);
		Database& setHashCompare(bt_comparator fun);
		int sync(u_int32_t flags=0);
		// comparators
		static int compareIString(DB *db, const DBT *dbt1, const DBT *dbt2);
		DB_CMP(Int8);
		DB_CMP(Int16);
		DB_CMP(Int32);
		DB_CMP(Int64);
		DB_CMP(UInt8);
		DB_CMP(UInt16);
		DB_CMP(UInt32);
		DB_CMP(UInt64);
		DB_CMP(Float);
		DB_CMP(Double);
		virtual int setCacheSize( u_int32_t gbytes, u_int32_t bytes, int ncache=0);
	};
#undef DB_CMP

template<typename T>
struct db_sorter
	{
	static Database::bt_comparator comparator()
		{
		return (Database::bt_comparator)NULL;
		}
	};
#define DB_SORTER(type,fun)  template<>\
struct db_sorter<type>\
{\
static Database::bt_comparator comparator()\
	{\
	return Database::compare##fun;\
	}\
};



DB_SORTER(int8_t,Int8)
DB_SORTER(int16_t,Int16)
DB_SORTER(int32_t,Int32)
DB_SORTER(int64_t,Int64)
DB_SORTER(uint8_t,UInt8);
DB_SORTER(uint16_t,UInt16)
DB_SORTER(uint32_t,UInt32)
DB_SORTER(uint64_t,UInt64)
DB_SORTER(float,Float)
DB_SORTER(double,Double)
#undef DB_SORTER
/**
 * Reference:
 *
 */
#define CURSOR_GET(fun,modifier) bool fun() { return get(modifier)==0;}

#define CURSOR_X(type,fun,side,dbt) inline type side##To##fun() const {\
		if(dbt.size< sizeof(type)) THROW("Data out of range");\
		type var;\
		std::memcpy((void*)&var,(void*)dbt.data,sizeof(type));\
		return var;\
		}
	
#define CURSOR_TO(type,fun) CURSOR_X(type,fun,key,keyE) CURSOR_X(type,fun,data,dataE)
class Cursor
	{
	private:
		DBC *cursorp;
		DBT keyE;
		DBT dataE;
	public:
		Cursor(DBC *cursor);
		virtual ~Cursor();
		virtual void close();
		int put(DBT *key, DBT *data, u_int32_t flags);
		DBT* key();
		DBT* data();
		std::auto_ptr<Entry> keyEntry();
		std::auto_ptr<Entry> dataEntry();
		int get(u_int32_t flags);
		int get(DBT *key, DBT *data, u_int32_t flags);
		std::pair<std::auto_ptr<Entry>,std::auto_ptr<Entry> > get(Entry *key,Entry* data, u_int32_t flags);
		CURSOR_GET(first,DB_FIRST)
		CURSOR_GET(last,DB_LAST)
		CURSOR_GET(next,DB_NEXT)
		CURSOR_GET(prev,DB_PREV)
		CURSOR_GET(nextDup,DB_NEXT_DUP)
		CURSOR_GET(nextNoDup,DB_NEXT_NODUP)
		CURSOR_GET(prevNoDup,DB_PREV_NODUP)
		CURSOR_TO(bool,Bool)
		CURSOR_TO(char,Char)
		CURSOR_TO(int8_t,Int8)
		CURSOR_TO(int16_t,Int16)
		CURSOR_TO(int32_t,Int32)
		CURSOR_TO(int64_t,Int64)
		CURSOR_TO(uint8_t,UInt8)
		CURSOR_TO(uint16_t,UInt16)
		CURSOR_TO(uint32_t,UInt32)
		CURSOR_TO(uint64_t,UInt64)
		CURSOR_TO(float,Float)
		CURSOR_TO(double,Double)
		std::string keyToString();
		std::string dataToString();
		void keyCopyTo(void* ptr,std::size_t index,std::size_t n) const;
		void dataCopyTo(void* ptr,std::size_t index,std::size_t n) const;
	};
#undef CURSOR_X
#undef CURSOR_TO
#undef CURSOR_GET

/**
 * BerkeleyDB environment
 */
class Environment
	{
	private:
		/* Env structure handle */
		DB_ENV *envptr;
		bool is_open;
	public:
		Environment();
		virtual ~Environment();
		DB_ENV* env();
		virtual void open( char *db_home, u_int32_t flags, int mode);
		virtual void close();
		virtual int setCacheSize( u_int32_t gbytes, u_int32_t bytes, int ncache=1);
	};
	
}

#endif
